#import "SHKeychain.h"

static NSString *kKeychainErrorDomain = @"kKeychainErrorDomain";

NSString *const kKeyChainUserId = @"kKeyChainUserId";
NSString *const kKeyChainToken = @"kKeyChainToken";
NSString *const kKeyChainTokenType = @"kKeyChainTokenType";

@implementation SHKeychain

+ (NSString *)defaultServiceName
{
    return NSBundle.mainBundle.bundleIdentifier;
}

+ (NSString *)appIdentifierPrefix
{
    return [self.availableAccessGroup componentsSeparatedByString:@"."].firstObject;
}

+ (NSString *)availableAccessGroup
{
    NSDictionary *query =
        [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, @"bundleSeedID",
                                                   kSecAttrAccount, @"", kSecAttrService, (id)kCFBooleanTrue,
                                                   kSecReturnAttributes, nil];
    CFDictionaryRef result = nil;
    OSStatus        status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
    if (status == errSecItemNotFound) {
        // 保证在锁屏状态下也能获取，所以要修改权限。
        NSMutableDictionary *addQuery = [query mutableCopy];
        [addQuery setObject:(__bridge_transfer NSString *)kSecAttrAccessibleAlways
                     forKey:(__bridge_transfer id)kSecAttrAccessible];
        status = SecItemAdd((__bridge CFDictionaryRef)addQuery, (CFTypeRef *)&result);
    }
    if (status != errSecSuccess) {
        if (result) {
            CFRelease(result);
        }
        return nil;
    }
    NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge id)(kSecAttrAccessGroup)];
    if (result) {
        CFRelease(result);
    }
    return accessGroup;
}


+ (NSString *)passwordForAccount:(NSString *)account
{
    return [self passwordForAccount:account service:[self defaultServiceName]];
}

+ (NSString *)passwordForAccount:(NSString *)account service:(NSString *)serviceName
{
    return [self passwordForAccount:account service:serviceName group:nil error:nil];
}

+ (NSString *)passwordForAccount:(NSString *)username
                         service:(NSString *)serviceName
                           group:(NSString *)keychainGroup
                           error:(NSError *__autoreleasing *)error
{
    [self clearOriginError:error];
    if (!username || !serviceName) {
        [self assignErrorWithCode:-2000 error:error];
        return nil;
    }

    NSMutableDictionary *query      = [self queryDictionaryService:serviceName group:keychainGroup account:username];
    CFTypeRef            attrResult = NULL;
    OSStatus             status     = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attrResult);
    if (attrResult) {
        CFRelease(attrResult);
    }
    if (status != noErr) {
        if (status != errSecItemNotFound) {
            [self assignErrorWithCode:status error:error];
        }
        return nil;
    }

    [query setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
    CFTypeRef resData  = NULL;
    status             = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&resData);
    NSData *resultData = (__bridge_transfer NSData *)resData;
    if (status != noErr) {
        if (status == errSecItemNotFound) {
            [self assignErrorWithCode:-1999 error:error];
        } else {
            [self assignErrorWithCode:status error:error];
        }
        return nil;
    }
    NSString *password = nil;
    if (resultData) {
        password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
    } else {
        [self assignErrorWithCode:-1999 error:error];
    }
    return password;
}
+ (BOOL)storeAccount:(NSString *)account password:(NSString *)password
{
    return [self storeAccount:account password:password service:[self defaultServiceName]];
}

+ (BOOL)storeAccount:(NSString *)account password:(NSString *)password service:(NSString *)serviceName
{
    return [self storeAccount:account
                     password:password
                      service:serviceName
          attributeAccessible:nil
                        group:nil
               updateExisting:YES
                forceUpdating:NO
                        error:nil];
}

+ (BOOL)storeAccount:(NSString *)account
            password:(NSString *)password
             service:(NSString *)serviceName
               group:(NSString *)keychainGroup
      updateExisting:(BOOL)updateExisting
               error:(NSError *__autoreleasing *)error
{
    return [self storeAccount:account
                     password:password
                      service:serviceName
          attributeAccessible:(__bridge_transfer NSString *)kSecAttrAccessibleAlways
                        group:keychainGroup
               updateExisting:updateExisting
                forceUpdating:NO
                        error:error];
}

+ (BOOL)storeAccount:(NSString *)account
            password:(NSString *)password
             service:(NSString *)serviceName
               group:(NSString *)keychainGroup
       forceUpdating:(BOOL)forceUpdating
               error:(NSError *__autoreleasing *)error
{
    return [self storeAccount:account
                     password:password
                      service:serviceName
          attributeAccessible:(__bridge_transfer NSString *)kSecAttrAccessibleAlways
                        group:keychainGroup
               updateExisting:YES
                forceUpdating:forceUpdating
                        error:nil];
}

+ (BOOL)deletePasswordForAccount:(NSString *)account
{
    return [self deletePasswordForAccount:account serviceName:[self defaultServiceName]];
}

+ (BOOL)deletePasswordForAccount:(NSString *)account serviceName:(NSString *)serviceName
{
    return [self deletePasswordForAccount:account serviceName:serviceName group:nil error:nil];
}

+ (BOOL)deletePasswordForAccount:(NSString *)username
                     serviceName:(NSString *)serviceName
                           group:(NSString *)keychainGroup
                           error:(NSError **)error
{
    if (!username || !serviceName) {
        [self assignErrorWithCode:-2000 error:error];
        return NO;
    }
    NSMutableDictionary *query = [self queryDictionaryService:serviceName group:keychainGroup account:username];
    [query setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnAttributes];
    return [self deleteItemsWithQuery:query error:error];
}

+ (BOOL)clearPasswordsForServiceName:(NSString *)serviceName group:(NSString *)keychainGroup error:(NSError **)error
{
    if (!serviceName) {
        [self assignErrorWithCode:-2000 error:error];
        return NO;
    }
    NSMutableDictionary *searchData = [self queryDictionaryService:serviceName group:keychainGroup account:nil];
    return [self deleteItemsWithQuery:searchData error:error];
}

#pragma mark Private Method
+ (BOOL)storeAccount:(NSString *)username
               password:(NSString *)password
                service:(NSString *)serviceName
    attributeAccessible:(NSString *)accessible
                  group:(NSString *)keychainGroup
         updateExisting:(BOOL)updateExisting
          forceUpdating:(BOOL)forceUpdating
                  error:(NSError *__autoreleasing *)error
{
    [self clearOriginError:error];
    if (!username || !password || !serviceName) {
        [self assignErrorWithCode:-2000 error:error];
        return NO;
    }
    NSError * getError         = nil;
    NSString *existingPassword = [self passwordForAccount:username
                                                  service:serviceName
                                                    group:keychainGroup
                                                    error:&getError];

    if ([getError code] == -1999) {
        getError = nil;
        [self deletePasswordForAccount:username serviceName:serviceName group:keychainGroup error:&getError];
        if ([getError code] != noErr) {
            if (error != nil) {
                *error = getError;
            }
            return NO;
        }
    } else if ([getError code] != noErr) {
        if (error != nil) {
            *error = getError;
        }
        return NO;
    }
    OSStatus             status = noErr;
    NSMutableDictionary *query  = [self queryDictionaryService:serviceName group:keychainGroup account:username];
    if (existingPassword) {
        if (forceUpdating || (![existingPassword isEqualToString:password] && updateExisting)) {
            query[(__bridge_transfer NSString *)kSecAttrLabel] = serviceName;
            CFDictionaryRef queryRef                           = (__bridge_retained CFDictionaryRef)query;
            CFDictionaryRef attributesToUpdate;
            if (accessible) {
                attributesToUpdate = (__bridge_retained CFDictionaryRef)[NSDictionary
                    dictionaryWithObjectsAndKeys:accessible, (__bridge_transfer NSString *)kSecAttrAccessible,
                                                 [password dataUsingEncoding:NSUTF8StringEncoding],
                                                 (__bridge_transfer NSString *)kSecValueData, nil];
            } else {
                attributesToUpdate = (__bridge_retained CFDictionaryRef)
                    [NSDictionary dictionaryWithObjectsAndKeys:[password dataUsingEncoding:NSUTF8StringEncoding],
                                                               (__bridge_transfer NSString *)kSecValueData, nil];
            }
            status = SecItemUpdate(queryRef, attributesToUpdate);
            CFRelease(queryRef);
            CFRelease(attributesToUpdate);
        }
    } else {
        query[(__bridge_transfer NSString *)kSecAttrLabel] = serviceName;
        query[(__bridge_transfer NSString *)kSecValueData] = [password dataUsingEncoding:NSUTF8StringEncoding];
        if (accessible) {
            query[(__bridge_transfer NSString *)kSecAttrAccessible] = accessible;
        }
        CFDictionaryRef queryRef = (__bridge_retained CFDictionaryRef)query;
        status                   = SecItemAdd(queryRef, NULL);
        CFRelease(queryRef);
    }
    return [self getResultWithCode:status error:error];
}

+ (BOOL)deleteItemsWithQuery:(NSDictionary *)searchData error:(NSError **)error
{
    [self clearOriginError:error];
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)searchData);
    return [self getResultWithCode:status error:error];
}

#pragma mark Tool Method
+ (void)clearOriginError:(NSError **)error
{
    if (error != nil) {
        *error = nil;
    }
}

+ (BOOL)getResultWithCode:(NSInteger)code error:(NSError **)error
{
    if (code != noErr) {
        [self assignErrorWithCode:code error:error];
        return NO;
    }
    return YES;
}

+ (void)assignErrorWithCode:(NSInteger)code error:(NSError **)error
{
    if (error != nil) {
        *error = [NSError errorWithDomain:kKeychainErrorDomain code:code userInfo:nil];
    }
}

+ (NSMutableDictionary *)queryDictionaryService:(NSString *)service group:(NSString *)group account:(NSString *)account
{
    NSMutableDictionary *query    = [NSMutableDictionary dictionary];
    query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
    if (account) {
        query[(__bridge id)kSecAttrAccount] = account;
    }
    if (service) {
        query[(__bridge id)kSecAttrService] = service;
    }
    if (group) {
        query[(__bridge id)kSecAttrAccessGroup] = group;
    }
    return query;
}

@end
